//
//  XYObject+KVO.m
//  KVO
//
//  Created by yao zhaofeng on 2018/4/4.
//  Copyright © 2018年 YaoZhaofeng. All rights reserved.
//
/*
 *自己实现KVO机制：
 *在注册监听的时候，对注册者动态的创建一个子类，然后将注册者的isa指针指向这个新的子类，并记录下观察者对象；与此同时重写被观察属性的setter方法。
 *在修改属性的同时，对观察者进行通知;
 *
 *在通知的时候，将对象的isa临时改为原来的类，让当观察者收到通知的时候，以为观察的还是原来的类。
 *同时重写class方法，让其依旧返回原来的类
 *
 *其中涉及：动态添加类； 动态向类中添加一个方法； 动态修改一个对象的isa指针； 动态的给对象发送消息； 动态获取方法的类型编码
 *
 */

#import "XYObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation XYObject (KVO)
- (void)XY_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    //动态添加一个类
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [@"XYKVO_" stringByAppendingString:oldClassName];
    
    Class myClass = objc_allocateClassPair([self class], [newClassName UTF8String], 0);
    
    /*添加setter方法，相当于重写setter方法， "v@:i" 含义 @: id   : SEL     v : void   (苹果类型编码：https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
     OC(消息发送机制),方法由两部分组成，方法编号@selector和方法实现(imp方法指针)，先找方法编号再得到方法的指针，再执行方法的代码块。
     */
    //下面两种实现都可以完成添加方法
    NSString *selStr = [NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]];
    class_addMethod(myClass, NSSelectorFromString(selStr), (IMP)setIntValue, "v@:i");
//    class_addMethod(myClass, @selector(setAge:), method_getImplementation(class_getInstanceMethod([self class], @selector(setAgeX:))), "v@:i");
    
    //动态添加class方法
    SEL classSEL = @selector(class);
    Method classM = class_getInstanceMethod(NSClassFromString(oldClassName), classSEL);
    const char * typeEncode =   method_getTypeEncoding(classM);
    
    //下面两种实现都可以完成添加方法
    class_addMethod(myClass, @selector(class), method_getImplementation(class_getInstanceMethod([self class], @selector(classX))), typeEncode);
//    class_addMethod(myClass, @selector(class), (IMP)class, typeEncode);
    
    //注册新添加的这个类
    objc_registerClassPair(myClass);
    
    //修改被观察这的isa指针，isa指针指向XYObject类改成指向myclass这个类
    object_setClass(self, myClass);
    
    //将观察者的属性保存到当前类里面去
    objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //    objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (Class)classX {
    Class myclass = object_getClass(self);
    return class_getSuperclass(myclass);
    
}

Class class(id self, SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

- (void)setAgeX:(int)age {
    Class myclass = [self class];
    int oldAge = [self age];

    object_setClass(self, class_getSuperclass(myclass));
    ((id(*)(id, SEL, int))objc_msgSend)(self, @selector(setAge:), age);
    
    //拿出观察者
    id objc = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
    
    //通知观察者
    NSDictionary *changeDic = @{NSKeyValueChangeNewKey: @(age), NSKeyValueChangeOldKey: @(oldAge)};
    ((id(*)(id, SEL, id, id, id, void*))objc_msgSend)(objc, @selector(observeValueForKeyPath:ofObject:change:context:), @"age", self, changeDic, nil);
    
    //改为子类
    object_setClass(self, myclass);
}

//相当于重写父类的方法
void setIntValue(id self, SEL _cmd, int value) {
    
    //保存当前类
//    Class myclass = [self class];
    Class myclass = object_getClass(self);
    //将self的isa指针指向父类
    object_setClass(self, class_getSuperclass(myclass));
    
    NSString *getName = [NSStringFromSelector(_cmd) substringFromIndex:3];
    getName = [getName substringToIndex:getName.length-1];
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList(class_getSuperclass(myclass), &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = propertyList[i];
        const char *propertyName = property_getName(property);
        NSString *pName = [NSString stringWithUTF8String:propertyName];
        if ([[getName lowercaseString] isEqualToString:[pName lowercaseString]]) {
            getName = pName;
            break;
        }
    }
    
    int oldValue = [[self valueForKey:getName] intValue];
    
    //调用父类
    ((id(*)(id, SEL, int))objc_msgSend)(self, _cmd, value);
    //    objc_msgSend(self, @selector(setAge:),age);
    
//    objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
//    objc_getClass(self);======> [self class]的结果
//
//    objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
//    objc_getClass(super->receiver)  ===> objc_getClass(self)
    
    //拿出观察者
    id objc = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
    
    //通知观察者
    NSDictionary *changeDic = @{NSKeyValueChangeNewKey: @(value), NSKeyValueChangeOldKey: @(oldValue)};
    ((id(*)(id, SEL, id, id, id, void*))objc_msgSend)(objc, @selector(observeValueForKeyPath:ofObject:change:context:), @"age", self, changeDic, nil);
    
    //改为子类
    object_setClass(self, myclass);
}
@end
